home *** CD-ROM | disk | FTP | other *** search
- ;; parlet.e zilla 19apr - dataparallel expression compiler
- ;; modified
- ;; 16june parletv macros work properly
- ;; 8june builtin v-arith ops are now scalar/vector overloaded
- ;; 4may comment
- ;; test expressions by: (pp (expand expr))
- ;;
- ;; possible bug in collect code: look is correct, but inner operators
- ;; are translated to vector, e.g. in:
- ;; (parlet (a) (unknown-scalar-func (- a b)))
- ;; - is incorrectly translated to v--
- ;;
- ;; (parlet <bindings> <body>)
- ;; <bindings> mention all variables (and functions) which body should
- ;; 'vectorize over'. Bindings are like a let list.
- ;; Any variables in body not mentioned in bindings are treated as scalars
- ;; and are promoted to vectors as necessary.
- ;; All variables mentioned in letbindings must have the same size.
- ;; Any functions in body not mentioned in bindings (and not known
- ;; to the compiler as builtin vector functions) are treated as scalar->scalar
- ;; functions and are replaced by a loop over vector arguments if necessary.
- ;; All functions in the body must be either scalar->scalar or
- ;; vector->vector, and the types of their arguments must match
- ;; after the elevation applied by this compiler.
- ;; Thus, can assume that the types (vector/scalar) of all functions are known.
- ;;>Mixed type scalar->vector expressions such as v-index should be moved
- ;; into the bindings, and vector->scalar expressions such as farray-ref
- ;; should be moved into a surrounding let.
- ;;
- ;; Why not just treat all vector-bound variables in body as vectors?
- ;; 1. Parallation lisp book p.21 argues that this is like dynamic scoping.
- ;; Dynamic typing is easy for an interpreter but hard for a compiler--
- ;; It can be difficult for a compiler to tell whether a variable currently
- ;; contains a vector (without doing some kind of type inferencing or
- ;; executing the program!).
- ;; With bindings the parallel variables are lexically obvious.
- ;;>Keep this approach--parlet will flag interesting places in the source
- ;; and will help compilation in the distant future.
- ;; 2. Making all functions accept any combination of scalar/vector
- ;; requires ugly programming--see ARITHOP--currently arith ops allow this
- ;; but comparisons do not. Simplifies fvector.c.
- ;; 3. It is not desirable to elevate all expressions involving vectors
- ;; to vector type. Parallation Lisp book gives an example of this:
- ;; x,y:lists; (elwise ((x)) (cons x y)) ==> ( (x_1 . y) (x_2 . y) ...)
- ;; versus (elwise ((x)(y)) (cons x y)) ==> ( (x_1 . y_1) (x_2 . y_2) ...)
- ;; It is less relevant here, because the only datatype we use is the
- ;; homogeneous foreign array.
- ;;
- ;; Consider vector->scalar, scalar->vector cases in more detail:
- ;;-vector->scalar e.g. farray-ref: This is only a problem if
- ;; the argument is used elsewhere in the same parlet as a vector,
- ;; e.g. (parlet ((v)) (func v (farray-length v)))
- ;; because, in this case, parlet does not know that farray-length returns
- ;; a scalar, and the resulting arg to func is not promoted.
- ;; This can be written as
- ;; (let ((len (farray-length v))) (parlet ((v)) (* v len)))
- ;;-scalar->vector e.g. (parlet ((v)) (* v (v-rnd n)))
- ;; This can be written as
- ;; (parlet ((v) (w (v-rnd n))) (* v w))
- ;; Both of these cases could be handled by putting more intelligence
- ;; into the v-ops: the vector->scalar would be solved by making
- ;; the v-ops elevate any scalar args to vector; the scalar->vector
- ;; case could be handled by having distribute return any vector argument
- ;; unchanged. Both cases make use of the fact that argument types
- ;; are easily known at run time in an interpreter.
- ;;
- ;; Desired behavior:
- ;; (parlet () (+ 2 v)) => (let () (+ 2 v))
- ;; (parlet ((v)) (+ 2 v)) => (v-+ (v-distribute 2 v) v)
- ;; (parlet ((v)) (+ v (+ 2 3))) => (v-+ v (v-distribute (+ 2 3) v))
- ;; NOT (v-+ v (v-+ (v-distribute 2) (v-dist....
- ;; (parlet ((v (% 1 2))) (* v 2) => (let ((v (% 1 2)))
- ;; (v-* v (v-distribute 2 v)))
- ;; (parlet ((v)) (+ x v)) => (v-+ (v-distribute x v) v)
- ;; (parlet (...) (set! v ...)) => set! is not vectorized
- ;;
- ;; To debug, use parlet[*]v form, or do (pp (macro-expand '(parlet...))).
- ;; Explicit v- functions of non-parlet variables are ok as long
- ;; as they reduce the vector to a scalar, for example:
- ;; (parlet ((v)) (set! v (+/ x))) => ok, but
- ;; (parlet ((v)) (set! v (v-index n))) => (set! v (distribute (v-index..
- ;; Express this as (parlet ((v (v-index n))) ...)
- ;; Explicit v- functions of parlet variables are ok when they
- ;; do not reduce the vector, but they are generally unneeded.
-
- (provide 'parlet.e)
-
- ;; generate debugging length-checking code at the beginning of each function?
- (define *parlet-gendebug* #f)
-
- ;; trace the compilation
- (define *parlet-trace* #f)
-
- ;; to identify nested parlets
- (if (not (bound? 'parlet-let))
- (define parlet-let let))
- (if (not (bound? 'parlet-let*))
- (define parlet-let* let*))
-
- ;; function translation table
- (define *parlet-functions*
- '(
- (append v-append)
-
- (sin v-sin)
- (cos v-cos)
- (sqrt v-sqrt)
- (exp v-exp)
- (abs v-abs)
- (not v-not)
- (rnd v-rnd)
- (pow v-pow)
- (truncate v-truncate)
-
- (* v-*)
- (+ v-+)
- (/ v-/)
- (- v--)
-
- (if v-select)
- (eq? v-eq)
- (eqv? v-eq)
- (equal? v-eq)
- (= v-eq)
- (< v-lt)
- (<= v-le)
- (> v-gt)
- (>= v-ge)
- (and v-and)
- (or v-or)
-
- ;side effects!
- (set! set!)
- ));parlet-functions
-
- ;; these functions (only) take any of 4 mixtures of scalar/vector arguments.
- (define *parlet-overloaded* '( + - * / min max fmod ))
-
- ;; generate unique symbols to avoid name capture
- (define *parlet-counter* 0)
- (define (parlet-gensym sym)
- (if (symbol? sym) (set! sym (symbol->string sym)))
- (set! *parlet-counter* (1+ *parlet-counter*))
- (string->symbol (string-append sym "$" (number->string *parlet-counter*))))
-
- (define (parlet-lookup sym)
- (assoc sym *parlet-functions*))
-
- ;; is symbol mentioned in bindings anywhere?
- (define (parlet-inbindings? sym bindings)
- (or (assoc sym bindings)
- (member sym bindings)))
-
- ;; is the function known to be a vector function?
- (define (parlet-vectorfunc? newe bindings)
- (let ((func (car newe)))
- (cond
- ((list? func) #f)
- ((parlet-lookup func) #t)
- ((parlet-inbindings? func bindings) #t)
- (#t #f)
- ))
- ) ;vectorfunc?
-
-
- ;;;;;;;;;;;;;;;; the top-level macros
-
- ;; decomposing this into parlet, parlet-make and passing the
- ;; let type (let,let*) to parlet-make is cleaner, but
- ;; this does not work with our mdefine system--elk macros
- ;; are interpreted at run time rather than at read time
- ;; unless you do something special. see .elkrc.
- (define-macro (parlet bindings . body)
- (let ((newbindings (parlet-newbindings bindings)))
- `(parlet-let ,newbindings
- ,@(if *parlet-gendebug* (parlet-debug bindings) '())
- ,@(parlet-compile-toplevel body bindings)
- ))
- );parlet
-
-
- ;; test version. is not a macro, just returns the result
- (define (parlett bindings . body)
- (let ((newbindings (parlet-newbindings bindings)))
- `(parlet-let ,newbindings
- ,@(if *parlet-gendebug* (parlet-debug bindings) '())
- ,@(parlet-compile-toplevel body bindings)
- ))
- );parlet
-
-
- (define-macro (parlet* bindings . body)
- (let ((newbindings (parlet-newbindings bindings)))
- `(let* ,newbindings
- ,@(if *parlet-gendebug* (parlet-debug bindings) '())
- ,@(parlet-compile-toplevel body bindings)
- ))
- );parlet
-
- ;; verbose/testing versions
- (define-macro (parletv bindings . body)
- (let ((e (macro-expand `(parlet ,bindings ,@body))))
- (pp e) (newline)
- (let ((newbindings (parlet-newbindings bindings)))
- `(let ,newbindings
- ,@(if *parlet-gendebug* (parlet-debug bindings) '())
- ,@(parlet-compile-toplevel body bindings)
- )
- )))
-
- (define-macro (parlet*v bindings . body)
- (let ((e (macro-expand `(parlet* ,bindings ,@body))))
- (pp e) (newline)
- (let ((newbindings (parlet-newbindings bindings)))
- `(let* ,newbindings
- ,@(if *parlet-gendebug* (parlet-debug bindings) '())
- ,@(parlet-compile-toplevel body bindings)
- )
- )))
-
- ;;(define-macro (parlet* bindings . body)
- ;; (parlet-make 'let* bindings body))
- ;;
- ;; beware, in calling this directly, body is a list of expressions,
- ;; not a single expression. if given a single expression,
- ;; it will come back with the outer parentheses stripped.
- (define (parlet-make lettype bindings body)
- (let ((newbindings (parlet-newbindings bindings)))
- `(,lettype ,newbindings
- ,@(if *parlet-gendebug* (parlet-debug bindings) '())
- ,@(cadr (parlet-compile body bindings)))
- )
- );parlet
-
-
- ; something like this would work in lisp but not in scheme -
- ; it leaves empty () or #fs in the list
- ;(define (parlet-newbindings bindings)
- ; (map (lambda (x) (if (> (length x) 1) x '()) ) bindings))
-
-
- ;; Bindings can contain new variables, e.g., (x (% 1 2 3))
- ;; and existing variables that are declared as parallel, e.g., (y).
- ;; Find the new variables and return a list of them so they
- ;; can be put in a let.
- (define (parlet-newbindings bindings)
- (let ((new '()))
- (dolist (i bindings)
- (if (and (list? i) (> (length i) 1))
- (set! new (cons i new))))
- (reverse new)))
-
-
- ;; generate vector length conformance debugging checks
- (define (parlet-debug bindings)
- (if (> (length bindings) 1)
- `((let ((len (farray-length ,(caar bindings))))
- ,@(map (lambda (x)
- `(if (not (equal? len (farray-length ,(car x))))
- (error 'vector "vectors size mismatch")))
- (cdr bindings))
- ))
- ))
-
-
- ;; indentation for compilation tracing
- (define parlet-reclevel 0)
- (define parlet-indentstr "")
-
- (define (parlet-indent)
- (set! parlet-reclevel (+ parlet-reclevel 2))
- (set! parlet-indentstr "")
- (dotimes (i parlet-reclevel)
- (set! parlet-indentstr (string-append parlet-indentstr " ")))
- )
-
- (define (parlet-dedent)
- (set! parlet-reclevel (- parlet-reclevel 2))
- (set! parlet-indentstr "")
- (dotimes (i parlet-reclevel)
- (set! parlet-indentstr (string-append parlet-indentstr " ")))
- )
-
- (define (parlet-trace msg . args)
- (if *parlet-trace*
- (apply format (cons #t (cons (string-append "~a" msg)
- (cons parlet-indentstr args)))))
- )
-
-
- ;; names of parlet forms.
- ;; used to look for nested parlets.
- (define *parlet-names* '(parlet-let parlet-let* parletv parlet*v))
-
-
- ;; the translator--translate the top-level parlet body
- ;; Unlike parlet-compile (below), do not elevate non-vector expressions
- (define (parlet-compile-toplevel body bindings)
- (map (lambda (x)
- (if (list? x)
- (cadr (parlet-compile x bindings))
- x))
- body)
- );compile-toplevel
-
-
- ;; translate one expression. called recursively.
- (define (parlet-compile e bindings)
- (parlet-trace "parlet-compile <~a> ~a~%" bindings e)
- (parlet-indent)
-
- (let* ((m #f) (types #f) (newe #f) (maxtype #f) (subparlet #f))
-
- ;; 1. change expression to a list of (type expression subparlet?),
- ;; recursively compile any subexpressions which are lists
- (set! m
- (map (lambda (x)
- (parlet-trace " map> ~a~%" x)
- (cond
- ((and (list? x) (not (null? x))
- (member (car x) *parlet-names*))
- (list 'vector x #t))
-
- ((list? x)
- (parlet-compile x bindings))
-
- (#t
- (list (parlet-type x bindings) x #f))
- );cond
- );lambda
- e);map
- );set!m
- (parlet-trace "parlet-compile m ~a~%" m)
-
- ;; extract the types from step 1.
- (set! types (map (lambda (x) (car x)) m))
- (parlet-trace "parlet-compile types ~a~%" types)
-
- ;; extract the expression from step 1.
- ;; this differs from e in that any sub-expressions are now
- ;; compiled
- (set! newe (map (lambda (x) (cadr x)) m))
- (parlet-trace "parlet-compile newe ~a~%" newe)
-
- (set! subparlet (if m (member #t (map (lambda (x) (list-ref x 2)) m))))
- (parlet-trace "parlet-compile subparlet ~a~%" subparlet)
- (if subparlet (error 'parlet "nested parlet not implemented"))
-
- ;; 2. see if any elements of the current expression are vector
- (set! maxtype (if (member 'vector types) 'vector #f))
- (parlet-trace "parlet-compile maxtype ~a~%" maxtype)
-
- ;; 3. if so, elevate all to vector, except arguments to one
- ;; of the *overloaded* functions, which can take mixed scalar/vector args.
- ;; If the function arg is an expression rather than a symbol,
- ;; this may needlessly distribute arguments to a *overloaded* function,
- ;; but this is just slower, not incorrect.
- ;;
- (if (equal? maxtype 'vector)
- (set! newe
- (if subparlet ;nested parlets?
- (parlet-compile-outer newe bindings maxtype)
- ;; else not nested
- (if (parlet-vectorfunc? newe bindings)
- ;; vectorized
- (parlet-compile-inner newe bindings maxtype types)
- ;; simulated vectorization
- (parlet-compile-innerloop newe bindings maxtype types)
- )
- ))
- );if
- (parlet-dedent)
-
- ;; return a list maxtype,newe to the caller
- (list maxtype newe (or (member (car newe) *parlet-names*)
- subparlet))
- );let*
- );-compile
-
-
-
- ;; a scalar func has been called with (some) vector args.
- ;; Expand into a simulated vector loop. Example:
- ;; (parlet (x) (f x)) ==>
- ;; (let* ((x-tmp x)
- ;; (len-tmp (farray-length x))
- ;; (collect-tmp (farray (farray-type x) len-tmp)))
- ;; (dolist (i-tmp len-tmp)
- ;; (let ((x (farray-ref x-tmp i-tmp)))
- ;; (farray-set! collect-tmp i-tmp
- ;; (f x))))
- ;; collect-tmp)
- ;;
- (define (parlet-compile-innerloop newe bindings maxtype types)
- (parlet-trace "parlet-compile-innerloop ~a~%" newe)
-
- (let* ((i (parlet-gensym "i"))
- (len (parlet-gensym "len"))
- (collect (parlet-gensym "collect"))
- (bind (parlet-outerbindings bindings i len)))
- `(let* (,@(car bind)
- (,collect (farray (farray-type ,(if (symbol? (car bindings))
- (car bindings) (caar bindings)))
- ,len))
- )
- (dotimes (,i ,len)
- (let* ,(list-ref bind 1)
- (farray-set! ,collect ,i ,newe)
- ))
- ,collect);quasilet
- )
- ) ;parlet-compile-innerloop
-
-
- ;; helper to compile-innerloop
- ;; return a list (outer,inner)
- ;; outer rename each bindings variable to a unique tmp variable
- ;; inner rebinds each bindings variable to a reference to outer tmps
- ;; example
- ;; bindings => ((x) (y (v-index res)))
- ;; ( ( (x-gen x) ;outer
- ;; (y-gen (v-index res)))
- ;; ( (x (farray-ref x-gen i-gen)) ;inner
- ;; (y (farray-ref y-gen i-gen)))
- ;; )
- (define (parlet-outerbindings bindings iter len)
- (parlet-trace "parlet-outerbindings~%")
- (let* ((firstsym (if (list? (car bindings)) (caar bindings) (car bindings)))
- (outer `((,len (farray-length ,firstsym))))
- (inner '()))
- (dolist (i bindings)
- (let* ((isym (if (list? i) (car i) i))
- (ialias (parlet-gensym isym)))
- ;(format #t "~a -> ~a~%" i ialias)
- (set! outer (cons
- (if (or (not (list? i)) (= (length i) 1))
- `(,ialias ,isym)
- `(,ialias ,(cadr i)))
- outer))
- (set! inner (cons `(,isym (farray-ref ,ialias ,iter)) inner))
- )
- )
- (list (reverse! outer) (reverse! inner))
- )) ;parlet-outerbindings
-
-
- ;; vectorize (non-nested or inner) parlet call
- (define (parlet-compile-inner newe bindings maxtype types)
- (parlet-trace "parlet-compile-inner {~a}~%" newe)
- (let ((needs-distribute
- (not (member (car newe) *parlet-overloaded*))))
- ;(format #t "~a needs-distribute ~a~%" newe needs-distribute)
- (set! newe
- (map (lambda (x t)
- (let ((functionp (eq? x (car newe))))
- (if (or functionp
- (and (not (eq? t 'vector)) needs-distribute))
- (parlet-elevate x maxtype bindings functionp)
- x)))
- newe types))
- (parlet-trace "parlet-compile-inner elevated ~a~%" newe)
- );let
- newe)
-
-
-
- ;; return the type of expression e;
- ;; bindings are let-type bindings which mention ALL parallel symbols.
- ;; Currently, types are 'vector for an farray or symbol mentioned
- ;; in the bindings, #f for everything else.
- (define (parlet-type e bindings)
- (cond
- ((list? e)
- (error 'parlet-type "foo"))
-
- ((symbol? e)
- (if (parlet-inbindings? e bindings)
- 'vector
- #f))
-
- ((farray? e) 'vector)
-
- (#t #f)
- );cond
- );-type
-
-
- ;; elevate expression to vector.
- ;; Pass in bindings because the size of expressions elevated
- ;; by distribute is needed and can be obtained from any of the
- ;; parlet-bound symbols.
- (define (parlet-elevate e typ bindings functionp)
- (parlet-trace "parlet-elevate: ~a~%" e)
- (if (not (equal? typ 'vector))
- (error 'parlet-elevate "logic error"))
-
- (cond
- ((list? e)
- `(v-distribute ,e ,(caar bindings)))
- ((symbol? e)
- (let ((newe (parlet-lookup e)))
- (if newe
- (cadr newe)
- (if (not functionp)
- `(v-distribute ,e ,(caar bindings))
- e)
- )))
- ((number? e)
- `(v-distribute ,e ,(caar bindings)))
- ((farray? e)
- e)
- (#t
- (format #t "warning: v-compiler does not recognize ~s~%" e)
- e)
- );cond
- );-elevate
-
- ;;;;;;;;;;;;;;;; NOT YET ;;;;;;;;;;;;;;;;
-
- ;; nice test expression for nested parlets.
- ;; how to avoid elevating the fp,y args to GR-wrrow?
- ;;
- (define parlet-test
- '(let ((x (v-index xres)))
- (parlet ((y (v-index yres)))
- (GR-wrrow fp y
- (parlet ((x)) (* x y)))
- 0.)
- ))
-
-
-
-